package wsi.match;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

import org.rygh.core.model.common.MapList;
import org.rygh.core.util.UIo;
import org.rygh.core.util.counter.SimpleCounter;
import org.rygh.core.xml.XmlPath;
import org.rygh.semantic.gui.swing.input.Input;
import org.rygh.semantic.model.ex.FactSet;
import org.rygh.semantic.model.rdf.Entity;

import wsi.dataguide.DataGuide;
import wsi.dataguide.tree.WSXmlLeaf;
import wsi.dataguide.tree.WSXmlParser;
import wsi.dataguide.tree.WSXmlTree;
import wsi.function.DirManager;
import wsi.subgraph.ExtractWorker;
import wsi.subgraph.tree.EntityTree;
import wsi.subgraph.tree.EntityTreeLoader;

public class SubtreeController {
	private final Logger log = Logger.getLogger(getClass().getName());

	private final MapList<Input, WSXmlLeaf> subrootMapList = new MapList<>();
	private final Map<Input, WSXmlTree> xmlTreeMap = new HashMap<>();

	private final SimpleCounter<WSXmlLeaf> commonRootCounter = new SimpleCounter<>();
	private final Matcher matcher = new Matcher();
	private final DirManager dir;

	private final MapList<WSXmlLeaf, WSXmlLeaf> leavesMapList = new MapList<>();
	private final MapList<Input, WSXmlLeaf> supleavesMapList = new MapList<>();

	private int subrootCount = 0;
	private boolean isSubrootAdded;
	private boolean valid;
	private Input currentInput;
	private WSXmlLeaf commonRoot;

	public SubtreeController(DirManager dir) {
		this.dir = dir;

		init(dir);
	}

	@Deprecated
	public void addSubroot(WSXmlLeaf subroot) {
		subrootMapList.put(currentInput, subroot);
		isSubrootAdded = true;
		subrootCount++;
		subroot.getValue().setValue(currentInput.getValue());
	}

	/**
	 * For functions like getArtistById, the XML contains one artist only. But
	 * for some inputs, some tags are repetitive, and erroneously detected as
	 * isomorphic root. However, if the average number of isomorphic subtrees
	 * per input is close to one, then we can judge that there is no isomorphic
	 * subtrees, or we use the XML root as the isomorphic subtree root.
	 * 
	 * @return
	 */
	public MapList<Input, WSXmlLeaf> computeIsomorphicRoot() {
		DataGuide dg = dir.getDataGuide();

		WSXmlLeaf common_root = null;
		WSXmlLeaf node_occur = null, node_root;
		List<WSXmlLeaf> occur_nodes = dg.getNodeWithMoreOccurrence();

		node_root = dg.getConcreteRoot();
		assert node_root != null;
		// node_child = dg.getNodeWithMaxChildren();

		int match_occur = 0, match_root;

		if (occur_nodes.isEmpty()) {
			log.info("Match result A");
			common_root = node_root;
		} else {

			for (WSXmlLeaf node : occur_nodes) {
				if (node.isLeaf())
					continue;

				int c = getMatchCount(node);

				// System.out.printf("node %s, match %d\n", node.getXpath(), c);

				if (match_occur < c) {
					match_occur = c;
					node_occur = node;
				}
			}

			if (node_occur == null || node_occur == node_root) {
				log.info(String.format(
						"Match result B: node %s match count %d, all nodes %s",
						node_occur, match_occur, occur_nodes));
				common_root = node_root;
			} else {
				match_root = getMatchCount(node_root);

				log.info(String.format(
						"Match result C: root (%d); occur (%d, %s);",
						match_root, match_occur, node_occur.getXpath()));

				double ratio = 1. * match_occur / match_root;

				/*
				 * 131026 middle confirmed.
				 */

				if (ratio > MatchProperty.Threshold.MIDDLE)
					common_root = node_occur;
				else
					common_root = node_root;
			}
		}

		// decided...

		subrootCount = 0;
		subrootMapList.clear();

		assert common_root != null;
		setCommonRoot(common_root);

		log.info(String.format("Subtree common root %s", common_root.getXpath()));

		for (Entry<Input, WSXmlTree> e : xmlTreeMap.entrySet()) {
			List<WSXmlLeaf> roots = e.getValue().getNodesByXPath(
					common_root.getXpath());

			subrootCount += roots.size();

			if (!roots.isEmpty())
				subrootMapList.put(e.getKey(), roots);
			else {
				// log.info(String.format("No subtrees for input %s",
				// e.getKey()));
			}
		}

		return subrootMapList;
	}

	public boolean containsCommonRoot(XmlPath xpath) {
		return xpath.presumesWithoutIndex(commonRoot.getXpath());
	}

	public Collection<List<WSXmlLeaf>> getAllSubroots() {
		return subrootMapList.getMap().values();
	}

	public WSXmlLeaf getCommonRoot() {
		return commonRoot;
	}

	public SimpleCounter<WSXmlLeaf> getCommonRootCounter() {
		return commonRootCounter;
	}

	public DirManager getDirManager() {
		return dir;
	}

	public List<WSXmlLeaf> getLeaves(WSXmlLeaf subroot) {
		return leavesMapList.get(subroot);
	}

	private int getMatchCount(WSXmlLeaf node) {
		// System.out.println("get match count for node " + node.getXpath());

		ExtractWorker extractor = new ExtractWorker(dir, null);
		int count = 0;

		for (Map.Entry<Input, WSXmlTree> ent : xmlTreeMap.entrySet()) {
			Input input = ent.getKey();
			WSXmlTree tree = ent.getValue();

			Entity entity = input.getEntity();

			if (entity == null) {
				log.info("No entity for input " + input.getValue());
				continue;
			}

			// System.out
			// .println("get match count for input " + entity.getValue());

			String fname = UIo.cleanForFilename(entity.getValue());
			FactSet facts = extractor.load(fname);

			if (facts == null || facts.isEmpty()) {
				System.out.printf("[error] no rdf subgraph for %s\n", fname);
				// assert false : fname;
				continue;
			}

			EntityTreeLoader loader = new EntityTreeLoader(entity, facts);
			EntityTree ent_tree = loader.load();

			if (ent_tree == null) {
				log.info("[warn] entity tree is null.");
				continue;
			}

			List<WSXmlLeaf> subroots = tree.getNodesByXPath(node.getXpath());
			SubtreeMatch match;

			for (WSXmlLeaf subroot : subroots) {
				// System.out.println("get match count for subroot "
				// + subroot.getXpath());

				List<WSXmlLeaf> leaves = subroot.getAllLeaves();

				match = matcher.match(input, ent_tree, null, leaves);
				count += match.size();
			}
		}

		return count;
	}

	public int getSubrootCount() {
		return subrootCount;
	}

	public List<WSXmlLeaf> getSubroots(Input input) {
		return subrootMapList.get(input);
	}

	public List<WSXmlLeaf> getSupLeaves(Input input) {
		return supleavesMapList.get(input);
	}

	public WSXmlTree getXmlTree(Input input) {
		return xmlTreeMap.get(input);
	}

	public Map<Input, WSXmlTree> getXmlTreeMap() {
		return xmlTreeMap;
	}

	private void init(DirManager dir) {
		Input[] inputs = dir.getInputs();
		Map<Input, File> fileMap = dir.getFileMap();

		for (Input input : inputs) {
			File file = fileMap.get(input);

			if (file == null) {
				log.info(String.format("[warn] no file for input %s", input));
				continue;
			}

			String xml = UIo.readFile(file);

			if (xml.isEmpty()) {
				log.info(String.format("Empty XML for %s", input));
				continue;
			}

			WSXmlParser parser = new WSXmlParser(input, xml, true);
			WSXmlTree tree = parser.parse();

			if (xmlTreeMap.containsKey(input)) {
				log.info("warn: duplicated input " + input);
			}

			xmlTreeMap.put(input, tree);
		}

	}

	public boolean isValid() {
		return valid;
	}

	/**
	 * 
	 * there are different cases we need to consider
	 * 
	 * 1. perfect case where each xml tree contains isomorphic subtrees. <br>
	 * 2. each xml tree contains exact one tree (no subtrees). <br>
	 * 3. some xml trees contain subtrees while some contain only one subtree. <br>
	 * 
	 * the algorithm:
	 * 
	 * 1. check if any subtree contains isomorphic subtrees. <br>
	 * 2. if yes, apply this master to the rest, if any. <br>
	 * 3. if no, take the root as the master. <br>
	 * 
	 * @param dataGuide
	 * @param inputs
	 * @param fileMap
	 * @return
	 */
	@Deprecated
	public boolean locate(DataGuide dataGuide, List<Input> inputs,
			Map<Input, File> fileMap) {
		WSXmlTree tree;
		WSXmlLeaf left, right;

		left = dataGuide.getConcreteRoot();

		Map<Input, WSXmlTree> treeMap = new HashMap<>();
		List<Input> leftover = new ArrayList<>();

		for (Input input : inputs) {
			currentInput = input;

			File f = fileMap.get(input);

			if (f == null)
				continue;

			log.info("locating subtrees for " + f.getName());

			String xml = UIo.readFile(f);

			if (xml.isEmpty()) {
				log.info(String.format("Empty XML for %s", input));
				continue;
			}

			WSXmlParser parser = new WSXmlParser(input, xml, true);
			tree = parser.parse();

			assert !xmlTreeMap.containsKey(input) : input;
			xmlTreeMap.put(input, tree);

			treeMap.put(input, tree);

			// System.out.println("xml tree " + tree);

			right = tree.getConcreteRoot();

			// System.out.printf("left %s\nright %s\n\n", left, right);

			// reset for each input value (new XML document)
			isSubrootAdded = false;
			left.contains(right, null, this, null, null);

			/*
			 * if we cannot find a master, add the root as a master
			 */

			if (!isSubrootAdded)
				leftover.add(input);

			// System.out
			// .printf("%s: found %d subtrees;\n", name, subroots.size());

		}

		boolean no_common_root = commonRoot == null;
		XmlPath common_root_path = null;

		if (no_common_root) {
			log.info("Use root as master:" + left.getXpath());
			setCommonRoot(left);
		} else {
			common_root_path = commonRoot.getXpath().getRelativePathsTo(
					new XmlPath("/dataguide"));
		}

		for (Input input : leftover) {
			currentInput = input;

			tree = treeMap.get(input);

			if (no_common_root) {
				right = tree.getConcreteRoot();

				log.info("Use root as subroot:" + right.getXpath());
			} else {
				// if there exists master, retrieve the subroot for XML trees
				// with one isomorphic subtree.

				right = tree.getNodeByXPath(common_root_path);

				// for example no results are found for the input (empty tree)
				if (right == null)
					continue;

				log.info("add corresponding subroot:" + right.getXpath());
			}

			addSubroot(right);

		}

		if (commonRoot == null) {
			log.info("No master node found.");
			valid = false;
			return false;
		}

		log.info(String.format("In total, %d subtrees are identified.",
				subrootCount));
		log.info(String.format("Master subroot: %s", commonRoot.getValue()));

		valid = true;
		return true;
	}

	public void putLeaves(WSXmlLeaf subroot, List<WSXmlLeaf> leaves) {
		leavesMapList.put(subroot, leaves);
	}

	public void putSupLeaves(Input input, List<WSXmlLeaf> leaves) {
		supleavesMapList.put(input, leaves);
	}

	public void setCommonRoot(WSXmlLeaf commonRoot) {
		this.commonRoot = commonRoot;
		commonRoot.getValue().setSelected(true);
		// log.info(String.format("Isomorphic subtree common root %s",
		// commonRoot.getXpath()));
	}

}
